package jsprintorbitor.experiment;

import java.util.ArrayList;
import java.util.List;

import jsprintorbitor.experiment.graph.AlgGenPair;
import jsprintorbitor.experiment.graph.GraphGenerateSettings;
import jsprintorbitor.experiment.graph.IGraphAlgorithm;
import laboratoryQ.computation.BaseUtils;
import laboratoryQ.computation.flatness.DiagramData;
import laboratoryQ.computation.flatness.SemanticDiagramData;
import laboratoryQ.experiment.BaseExperiment;
import laboratoryQ.experiment.IExperimentResult;
import laboratoryQ.experiment.process.IExperimentContext;

public class SpOrbExperiment extends BaseExperiment {
	@Override
	protected IExperimentResult runExperimentImplementation(
			IExperimentContext icontext,
			IExpRunStatusHandler expRunStatusHandler
	) throws StopExperimentException
	{
		SpOrbExperimentContext context = (SpOrbExperimentContext) icontext;
		SpOrbExperimentSettings settings = context.getExperimentSettings();

		int[] progressDataPtr = computeProgressData(settings);
		runExperiments(context, expRunStatusHandler, progressDataPtr);

		expRunStatusHandler.handleExperimentResult(context);
		return context;
	}

	private int[] computeProgressData(SpOrbExperimentSettings settings) {
		int totalProgressStepsCount = 0;
		if (settings.gtrvexSzRngEnabled) {
			totalProgressStepsCount +=
				settings.gtrvexSzRngVrtCntEnd -
				settings.gtrvexSzRngVrtCntBeg + 1;
		}
		if (settings.gtrvexPwRngEnabled) {
			totalProgressStepsCount += computePowerRangeSteps(
					settings.gtrvexPwRngEnd,
					settings.gtrvexPwRngBeg,
					settings.gtrvexPwRngStep
			);
		}
		if (settings.dejkstraPwRngEnabled) {
			totalProgressStepsCount += computePowerRangeSteps(
					settings.dejkstraPwRngEnd,
					settings.dejkstraPwRngBeg,
					settings.dejkstraPwRngStep
			);
		}
		if (settings.dinicPwRngEnabled) {
			totalProgressStepsCount += computePowerRangeSteps(
					settings.dinicPwRngEnd,
					settings.dinicPwRngBeg,
					settings.dinicPwRngStep
			);
		}
		if (settings.mcfPwRngEnabled) {
			totalProgressStepsCount += computePowerRangeSteps(
					settings.dinicPwRngEnd,
					settings.dinicPwRngBeg,
					settings.dinicPwRngStep
			);
		}
		return new int[] { 0, totalProgressStepsCount };
	}

	private void runExperiments(
			SpOrbExperimentContext context,
			IExpRunStatusHandler expRunStatusHandler,
			int[] progressDataPtr
	) throws StopExperimentException
	{
		SpOrbExperimentSettings settings = context.getExperimentSettings();
		if (settings.gtrvexSzRngEnabled) {
			runGtrvexSzRngExperiment(context, expRunStatusHandler, progressDataPtr);
		}
		if (settings.gtrvexPwRngEnabled) {
			runGtrvexPwRngExperiment(context, expRunStatusHandler, progressDataPtr);
		}
		if (settings.dejkstraPwRngEnabled) {
			runDejkstraPwRngExperiment(context, expRunStatusHandler, progressDataPtr);
		}
		if (settings.dinicPwRngEnabled) {
			runDinicPwRngExperiment(context, expRunStatusHandler, progressDataPtr);
		}
		if (settings.mcfPwRngEnabled) {
			runMCFPwRngExperiment(context, expRunStatusHandler, progressDataPtr);
		}
	}

	private static int computePowerRangeSteps(
			double gtrvexPwRngEnd,
			double gtrvexPwRngBeg,
			double gtrvexPwRngStep
	) {
		double dist = gtrvexPwRngEnd - gtrvexPwRngBeg;
		return (int) Math.floor(dist / gtrvexPwRngStep + 1);
	}

	private static void onNextStepProcessed(
			IExpRunStatusHandler expRunStatusHandler,
			int[] progressDataPtr
	) throws StopExperimentException
	{
		progressDataPtr[0]++;
		expRunStatusHandler.stepsProcessed(progressDataPtr[0], progressDataPtr[1]);
	}

	private static void runGtrvexSzRngExperiment(
			SpOrbExperimentContext context,
			IExpRunStatusHandler expRunStatusHandler,
			int[] progressDataPtr
	) throws StopExperimentException
	{
		SpOrbExperimentSettings settings = context.getExperimentSettings();

		int vcBeg = settings.gtrvexSzRngVrtCntBeg;
		int vdEnd = settings.gtrvexSzRngVrtCntEnd;
		int sizesPopulation = vdEnd - vcBeg + 1;
		AlgGenPair[] traversalExperimentCases = getTraversalExperimentCases(settings);
		int ecCount = traversalExperimentCases.length;
		int eachSzExperimentCount = settings.gtrvexSzRngExpRunCnt;

		double[][] mearusedTime = new double[ecCount][sizesPopulation];
		double[] vertCount = new double[sizesPopulation];
		GraphGenerateSettings grGenSettings = new GraphGenerateSettings();

		for (int vertsCount = vcBeg; vertsCount <= vdEnd; vertsCount++) {
			grGenSettings.verticesCount = vertsCount;
			grGenSettings.edgesCount = computeEdgesCount(
					vertsCount,
					settings.gtrvexSzRngV2EPower,
					settings.gtrvexSzRngV2EMul,
					settings.gtrvexSzRngV2EAdd);
			int vgNo = vertsCount - vcBeg;

			for (int eci = 0; eci < ecCount; eci++) {
				AlgGenPair ec = traversalExperimentCases[eci];
				grGenSettings.randomInt = BaseUtils.randomInt(context.getRandom(), 1 << 30);
				INativeObjectHolder graph = ec.generator.generateGraph(grGenSettings);

				try {
					long timePre = System.currentTimeMillis();
					runAlgorithmOnGraph(ec.algorithm, graph, eachSzExperimentCount);
					long timePost = System.currentTimeMillis();

					double time = timePost - timePre;
					time /= eachSzExperimentCount;
					mearusedTime[eci][vgNo] = time;
				} finally {
					graph.dispose();
				}
			}
			vertCount[vgNo] = vertsCount;
			onNextStepProcessed(expRunStatusHandler, progressDataPtr);
		}

		DiagramData[] diagrams = new DiagramData[ecCount];
		for (int eci = 0; eci < ecCount; eci++) {
				diagrams[eci] = new SemanticDiagramData(vertCount, mearusedTime[eci],
						traversalExperimentCases[eci]);
		}
		context.getTimeMearurements().add(
				new SpOrbExperimentTimeMearurement(
						"Gr-trav-SZragne", diagrams));
	}

	private static AlgGenPair[] getTraversalExperimentCases(SpOrbExperimentSettings settings) {
		List<AlgGenPair> result = new ArrayList<AlgGenPair>();
		BaseUtils.addAll(result, settings.traversalExperimentCases);
		if (settings.trvVirtualExpCasesEnabled)
		{
			BaseUtils.addAll(result, settings.traversalExperimentCasesVirtual);
		}
		return result.toArray(new AlgGenPair[result.size()]);
	}

	private static void runGtrvexPwRngExperiment(
			SpOrbExperimentContext context,
			IExpRunStatusHandler expRunStatusHandler,
			int[] progressDataPtr
	) throws StopExperimentException
	{
		SpOrbExperimentSettings settings = context.getExperimentSettings();

		int powersPopulation = computePowerRangeSteps(
				settings.dejkstraPwRngEnd,
				settings.dejkstraPwRngBeg,
				settings.dejkstraPwRngStep
		);
		AlgGenPair[] traversalExperimentCases = getTraversalExperimentCases(settings);
		int ecCount = traversalExperimentCases.length;
		int eachSzExperimentCount = settings.gtrvexPwRngExpRunCnt;

		double[][] mearusedTime = new double[ecCount][powersPopulation];
		double[] powers = new double[powersPopulation];
		GraphGenerateSettings grGenSettings = new GraphGenerateSettings();
		grGenSettings.verticesCount = settings.gtrvexPwRngVrtCnt;

		for (int step = 0; step < powersPopulation; step++) {
			double power = settings.gtrvexPwRngBeg + step * settings.gtrvexPwRngStep;
			grGenSettings.edgesCount = computeEdgesCount(
					grGenSettings.verticesCount,
					power,
					settings.gtrvexPwRngMul,
					settings.gtrvexPwRngAdd);

			for (int eci = 0; eci < ecCount; eci++) {
				AlgGenPair ec = traversalExperimentCases[eci];
				grGenSettings.randomInt = BaseUtils.randomInt(context.getRandom(), 1 << 30);
				INativeObjectHolder graph = ec.generator.generateGraph(grGenSettings);

				try {
					long timePre = System.currentTimeMillis();
					runAlgorithmOnGraph(ec.algorithm, graph, eachSzExperimentCount);
					long timePost = System.currentTimeMillis();

					double time = timePost - timePre;
					time /= eachSzExperimentCount;
					mearusedTime[eci][step] = time;
				} finally {
					graph.dispose();
				}
			}
			powers[step] = power;
			onNextStepProcessed(expRunStatusHandler, progressDataPtr);
		}

		DiagramData[] diagrams = new DiagramData[ecCount];
		for (int eci = 0; eci < ecCount; eci++) {
			diagrams[eci] = new SemanticDiagramData(powers, mearusedTime[eci],
					traversalExperimentCases[eci]);
		}
		context.getTimeMearurements().add(
				new SpOrbExperimentTimeMearurement(
						"Gr-trav-PWragne", diagrams));
	}

	private static void runDejkstraPwRngExperiment(
			SpOrbExperimentContext context,
			IExpRunStatusHandler expRunStatusHandler,
			int[] progressDataPtr
	) throws StopExperimentException
	{
		SpOrbExperimentSettings settings = context.getExperimentSettings();

		int powersPopulation = computePowerRangeSteps(
				settings.dejkstraPwRngEnd,
				settings.dejkstraPwRngBeg,
				settings.dejkstraPwRngStep
		);
		int ecCount = settings.shortestPathExperimentCases.length;
		int eachSzExperimentCount = settings.dejkstraPwRngExpRunCnt;

		double[][] mearusedTime = new double[ecCount][powersPopulation];
		double[] powers = new double[powersPopulation];
		GraphGenerateSettings grGenSettings = new GraphGenerateSettings();
		grGenSettings.verticesCount = settings.dejkstraPwRngVrtCnt;

		for (int step = 0; step < powersPopulation; step++) {
			double power = settings.dejkstraPwRngBeg + step * settings.dejkstraPwRngStep;
			grGenSettings.edgesCount = computeEdgesCount(
					grGenSettings.verticesCount,
					power,
					settings.dejkstraPwRngMul,
					settings.dejkstraPwRngAdd);
			
			for (int eci = 0; eci < ecCount; eci++) {
				AlgGenPair ec = settings.shortestPathExperimentCases[eci];
				grGenSettings.randomInt = BaseUtils.randomInt(context.getRandom(), 1 << 30);
				INativeObjectHolder graph = ec.generator.generateGraph(grGenSettings);

				try {
					long timePre = System.currentTimeMillis();
					runAlgorithmOnGraph(ec.algorithm, graph, eachSzExperimentCount);
					long timePost = System.currentTimeMillis();

					double time = timePost - timePre;
					time /= eachSzExperimentCount;
					mearusedTime[eci][step] = time;
				} finally {
					graph.dispose();
				}
			}
			powers[step] = power;
			onNextStepProcessed(expRunStatusHandler, progressDataPtr);
		}

		DiagramData[] diagrams = new DiagramData[ecCount];
		for (int eci = 0; eci < ecCount; eci++) {
			diagrams[eci] = new SemanticDiagramData(powers, mearusedTime[eci],
					settings.shortestPathExperimentCases[eci]);
		}
		context.getTimeMearurements().add(
				new SpOrbExperimentTimeMearurement(
						"Shortest-parth-PWragne", diagrams));
	}

	private static void runDinicPwRngExperiment(
			SpOrbExperimentContext context,
			IExpRunStatusHandler expRunStatusHandler,
			int[] progressDataPtr
	) throws StopExperimentException
	{
		SpOrbExperimentSettings settings = context.getExperimentSettings();

		int powersPopulation = computePowerRangeSteps(
				settings.dinicPwRngEnd,
				settings.dinicPwRngBeg,
				settings.dinicPwRngStep
		);
		int ecCount = settings.maxFlowExperimentCases.length;
		int eachSzExperimentCount = settings.dinicPwRngExpRunCnt;

		double[][] mearusedTime = new double[ecCount][powersPopulation];
		double[] powers = new double[powersPopulation];
		GraphGenerateSettings grGenSettings = new GraphGenerateSettings();
		grGenSettings.verticesCount = settings.dinicPwRngVrtCnt;

		for (int step = 0; step < powersPopulation; step++) {
			double power = settings.dinicPwRngBeg + step * settings.dinicPwRngStep;
			grGenSettings.edgesCount = computeEdgesCount(
					grGenSettings.verticesCount,
					power,
					settings.dinicPwRngMul,
					settings.dinicPwRngAdd);

			for (int eci = 0; eci < ecCount; eci++) {
				AlgGenPair ec = settings.maxFlowExperimentCases[eci];
				grGenSettings.randomInt = BaseUtils.randomInt(context.getRandom(), 1 << 30);
				INativeObjectHolder graph = ec.generator.generateGraph(grGenSettings);

				try {
					long timePre = System.currentTimeMillis();
					runAlgorithmOnGraph(ec.algorithm, graph, eachSzExperimentCount);
					long timePost = System.currentTimeMillis();
					double time = timePost - timePre;

					time /= eachSzExperimentCount;
					mearusedTime[eci][step] = time;
				} finally {
					graph.dispose();
				}
			}
			powers[step] = power;
			onNextStepProcessed(expRunStatusHandler, progressDataPtr);
		}
		
		DiagramData[] diagrams = new DiagramData[ecCount];
		for (int eci = 0; eci < ecCount; eci++) {
			diagrams[eci] = new SemanticDiagramData(powers, mearusedTime[eci],
					settings.maxFlowExperimentCases[eci]);
		}
		if (ecCount > 0) {
			context.getTimeMearurements().add(
					new SpOrbExperimentTimeMearurement(
							"Max-Flow-PWragne", diagrams));
		}
	}

	private static void runMCFPwRngExperiment(
			SpOrbExperimentContext context,
			IExpRunStatusHandler expRunStatusHandler,
			int[] progressDataPtr
	) throws StopExperimentException
	{
		SpOrbExperimentSettings settings = context.getExperimentSettings();
		
		int powersPopulation = computePowerRangeSteps(
				settings.dinicPwRngEnd,
				settings.dinicPwRngBeg,
				settings.dinicPwRngStep
		);
		int ecCount = settings.minCostFlowExperimentCases.length;
		int eachSzExperimentCount = settings.dinicPwRngExpRunCnt;
		
		double[][] mearusedTime = new double[ecCount][powersPopulation];
		double[] powers = new double[powersPopulation];
		GraphGenerateSettings grGenSettings = new GraphGenerateSettings();
		grGenSettings.verticesCount = settings.dinicPwRngVrtCnt;
		
		for (int step = 0; step < powersPopulation; step++) {
			double power = settings.dinicPwRngBeg + step * settings.dinicPwRngStep;
			grGenSettings.edgesCount = computeEdgesCount(
					grGenSettings.verticesCount,
					power,
					settings.dinicPwRngMul,
					settings.dinicPwRngAdd);
			
			for (int eci = 0; eci < ecCount; eci++) {
				AlgGenPair ec = settings.minCostFlowExperimentCases[eci];
				grGenSettings.randomInt = BaseUtils.randomInt(context.getRandom(), 1 << 30);
				INativeObjectHolder graph = ec.generator.generateGraph(grGenSettings);
				
				try {
					long timePre = System.currentTimeMillis();
					runAlgorithmOnGraph(ec.algorithm, graph, eachSzExperimentCount);
					long timePost = System.currentTimeMillis();
					double time = timePost - timePre;
					
					time /= eachSzExperimentCount;
					mearusedTime[eci][step] = time;
				} finally {
					graph.dispose();
				}
			}
			powers[step] = power;
			onNextStepProcessed(expRunStatusHandler, progressDataPtr);
		}
		
		DiagramData[] diagrams = new DiagramData[ecCount];
		for (int eci = 0; eci < ecCount; eci++) {
			diagrams[eci] = new SemanticDiagramData(powers, mearusedTime[eci],
					settings.maxFlowExperimentCases[eci]);
		}
		if (ecCount > 0) {
			context.getTimeMearurements().add(
					new SpOrbExperimentTimeMearurement(
							"Max-Flow-PWragne", diagrams));
		}
	}

	private static int computeEdgesCount(int vertsCount, double pow, double mul, double add) {
		double dedges = Math.exp(Math.log(vertsCount) * pow);
		dedges *= mul;
		dedges += add;
		return (int) dedges;
	}

	private static void runAlgorithmOnGraph(
			IGraphAlgorithm algorithm,
			INativeObjectHolder graph,
			int count
	) {
		for (int i = 0; i < count; i++) {
			algorithm.runForGraph(graph);
		}
	}
}
